/*********************************************************************************
 *      Copyright:  (C) 2022 Zhukaifa（928085237@qq.com）
 *                  All rights reserved.
 *
 *       Filename: com_func.c 
 *       
 *    Description:   com port related operations

 *                 
 *        Version:  1.0.0(28/07/22)
 *         Author:  Zhukaifa（928085237@qq.com）
 *      ChangeLog:  1, Release initial version on "28/07/22 21:52:04"
 *                 
 ********************************************************************************/


#include "com_func.h"
#include "logger.h"


/*********************************************************************************
 *
 *           Name:      com_open 
 *
 *    Description:      Open the com port device according to the com port path
 *                 
 *        Input args:   fname : com port path 
 *
 *       Output args: 
 *
 *      return value:  
 *                      =0    success
 *                      <0    failure
 *                 
 ********************************************************************************/



int com_open(char *fname)
{
    int fd, rv;

    if(NULL == fname)
    {
        log_error("%s,Invalid parameter\n",__func__);
        return -1;
    }

    if((fd = open(fname,O_RDWR|O_NOCTTY|O_NDELAY)) < 0)
    {
        log_error("Open %s failed: %s\n",fname, strerror(errno));

        return -1;
    }

    /* Judge whether the serial port is blocked*/
    if((rv = fcntl(fd, F_SETFL, 0)) < 0)
    {
        log_error("fcntl failed!\n");

        return -2;
    }
    
	/* Is it a terminal device*/
    if(0 == isatty(fd))  
    {
        log_error("%s:[%d] is not a Terminal equipment.\n", fname, fd);
        return -3;
    }
    
    log_info("Open %s successfully!\n", fname);

    return fd;
} 
 
 
/*********************************************************************************
 *
 *           Name:     com_init
 *
 *    Description:      Initialize the serial port  
 *                 
 *        Input args:  com:  Structure for storing com port attributes
 *
 *       Output args:  Set com port properties in the structure
 *
 *      return value:
 *
 *                      =0    success
 *                      <0    failure

 *                 
 ********************************************************************************/


int com_init(comport_t *com)
{
    char                  baudrate[32] = {0};
    struct termios        newtermios;

    memset(&newtermios,0,sizeof(struct termios));
    memset((&com->oldtermios),0,sizeof(struct termios));

    if(!com)
    {
        log_error("%s invalid parameter.\n", __func__);
        return -1;
    }

    /*  Get default serial port properties*/
    if(tcgetattr(com->fd, &(com->oldtermios)))
    {
        log_error("%s, get termios to oldtermios failure:%s\n",__func__,strerror(errno));
        return -2;
    }

    /* Modify the default attribute to become a new attribute*/
    if(tcgetattr(com->fd, &newtermios))
    {
        log_error("%s, get termios to newtermios failure:%s\n",__func__,strerror(errno));
        return -3;
    }
    

    /*  Modify the control mode to ensure that the program will not occupy the serial port*/
    newtermios.c_cflag |= CLOCAL;


    /* Start receiver capable of reading input data from serial port */
    newtermios.c_cflag |= CREAD;

	/* Convert '\n' to '\r'*/
	newtermios.c_oflag |= ONLCR;


    /*
     * ICANON: 标准模式
     * ECHO: 回显所输入的字符
     * ECHOE: 如果同时设置了ICANON标志，ERASE字符删除前一个所输入的字符，WERASE删除前一个输入的单词
     * ISIG: 当接收到INTR/QUIT/SUSP/DSUSP字符，生成一个相应的信号
     *
     * 在原始模式下，串口输入数据是不经过处理的，在串口接口接收的数据被完整保留。
     
     * */
    newtermios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);


    /*
     * BRKINT: BREAK将会丢弃输入和输出队列中的数据(flush)，并且如果终端为前台进程组的控制终端，则BREAK将会产生一个SIGINT信号发送到这个前台进程组
     * ICRNL: 将输入中的CR转换为NL
     * INPCK: 允许奇偶校验
     * ISTRIP: 剥离第8个bits
     * IXON: 允许输出端的XON/XOF流控
     *
     * */
    newtermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);


    /* --------设置数据流控制------- */
    switch(com->flow_ctrl)
    {
        case 0:                         //不使用流控制
            newtermios.c_cflag &=~CRTSCTS;
            break;

        case 1:                         //使用硬件流控制
            newtermios.c_cflag |= CRTSCTS;
            break;

        case 2:                         //使用软件流控制
            newtermios.c_cflag |= IXON| IXOFF|IXANY;
            break;

        default:
            newtermios.c_cflag &=~CRTSCTS;
            break;
    }


    /* 设置波特率，否则默认设置其为B115200 */
    if(com->baud_rate)
    {
        sprintf(baudrate,"B%d",com->baud_rate);

        cfsetispeed(&newtermios, (int)baudrate); //设置输入输出波特率
        cfsetospeed(&newtermios, (int)baudrate);
    }
    else
    {
        cfsetispeed(&newtermios, B115200);
        cfsetospeed(&newtermios, B115200);
    }


    /* ------设置数据位-------*/
    newtermios.c_cflag &= ~CSIZE;   //先把数据位清零，然后再设置新的数据位

    switch(com->data_bits)
    {
        case '5':
            newtermios.c_cflag |= CS5;
            break;

        case '6':
            newtermios.c_cflag |= CS6;
            break;

        case '7':
            newtermios.c_cflag |= CS7;
            break;

        case '8':
            newtermios.c_cflag |= CS8;
            break;

        default:
            newtermios.c_cflag |= CS8;  //deafult data bit is 8
            break;
    }

    /* -------设置校验方式------- */
    switch(com->parity)
    {
        /* 无校验 */
        case 'n':
        case 'N':
            newtermios.c_cflag &= ~PARENB;
            newtermios.c_iflag &= ~INPCK;
            break;

        /* 偶校验 */
        case 'e':
        case 'E':
            newtermios.c_cflag |= PARENB;
            newtermios.c_cflag &= ~PARODD;
            newtermios.c_iflag |= INPCK;
            break;

        /* 奇校验 */
        case 'o':
        case 'O':
            newtermios.c_cflag |= (PARODD | PARENB);
            newtermios.c_iflag |= INPCK;

        /* 设置为空格 */
        case 's':
        case 'S':
            newtermios.c_cflag &= ~PARENB;
            newtermios.c_cflag &= ~CSTOPB;

        /* 默认无校验 */
        default:
            newtermios.c_cflag &= ~PARENB;
            newtermios.c_iflag &= ~INPCK;
            break;

    }

    /* -------设置停止位-------- */
    switch(com->stop_bits)
    {
        case '1':
            newtermios.c_cflag &= ~CSTOPB;
            break;

        case '2':
            newtermios.c_cflag |= CSTOPB;
            break;

        default:
            newtermios.c_cflag &= ~CSTOPB;
            break;
    }

    /* OPOST: 表示处理后输出，按照原始数据输出 */
    newtermios.c_oflag &= ~(OPOST);


    newtermios.c_cc[VTIME] = 0;  //最长等待时间
    newtermios.c_cc[VMIN] = 0;  //最小接收字符


    /* 刷新串口缓冲区 / 如果发生数据溢出，接收数据，但是不再读取*/
    if(tcflush(com->fd,TCIFLUSH))
    {
        log_error("%s, clear the cache failure:%s\n", __func__, strerror(errno));
        return -4;
    }

    /* 设置串口属性，立刻生效 */
    if(tcsetattr(com->fd,TCSANOW,&newtermios) != 0)
    {
        log_error("%s, tcsetcom failure: %s\n", __func__, strerror(errno));
        return -5;
    }

    log_info("com port Init Successfully!\n");

    return 0;
} 
 
 
/*********************************************************************************
 *
 *           Name:      com_send
 *
 *    Description:  Send at command to com port
 *                 
 *        Input args:     
 *                      fd:   File descriptor
 *                      msg:  At instruction 
 *                      msg_len:  Length of at instruction
 *
 *       Output args: 
 *
 *      return value:
 *
 *                      =0    success
 *                      <0    failure

 *
 *                 
 ********************************************************************************/


int com_send (int fd, char *msg, int msg_len)
{
    int rv = 0;

	if((fd<0)||!msg|| (msg_len<0))
	{
		log_error("%s The argument invalid!\n", __func__);
		return -1;
	}


    rv = write(fd, msg, msg_len);
    if(rv == msg_len)
    {
        return rv;
    }
    else
    {
        tcflush(fd, TCOFLUSH);
		log_error("%s write message failure: %s\n", __func__, strerror(errno));

        return -1;
    }

    return rv;
} 
 
 
/*********************************************************************************
 *
 *           Name:  com_recv 
 *    Description:  Receive the reply of at instruction
 *                 
 *        Input args:     
 *
 *                      fd:   File descriptor
 *                      buf_len:  Length of buf
 *                      timeout: Longest Waiting for response time
 *       Output args: 
 *                      r_buf: Store the reply of AT instruction
 *
 *      return value:
 *                      =0    success
 *                      <0    failure

 *
 *                 
 ********************************************************************************/


int com_recv(int fd, char *r_buf, int buf_len, int timeout) 
{
	int      rv;
	fd_set   rset;
	struct   timeval timeout_s;

	if((fd<0)||!r_buf|| (buf_len<0)|| (timeout<0))
	{
		log_error("%s The argument invalid!\n", __func__);
		return -1;
	}


	timeout_s.tv_sec = (time_t)(timeout/1000);//s
	timeout_s.tv_usec= 0;                     //us
	
	FD_ZERO(&rset);//clears the set

	FD_SET(fd, &rset);//Adds the file descriptor fd to the rset
	/*If a file in the rset is readable, a value greater than 0 is returned to indicate that a file is readable*/


	rv = select(fd+1, &rset, NULL, NULL, &timeout_s);//non-blocking

	if(rv < 0)
	{

		log_error("select() failure");

		return -1;

	}

	else if(rv == 0)
	{

		log_error("select() time out!\n");

		return -2;
	}


	if(FD_ISSET(fd, &rset))
	{

		rv = read(fd, r_buf, buf_len);
		if(rv < 0)
		{
			log_error(" read data failure!:%s\n", strerror(errno));
			return -3;
		}
	}

	return 0;

}
 
  
/*********************************************************************************
 *
 *              Name: com_close
 *
 *       Description:  
 *                      Clear the cache and re assign the original serial port attribute to the serial port,
 *                      and then close it
 *                 
 *        Input args:     
 *                      com:  Structure for storing com port attributes
 *
 *       Output args: 
 *
 *      return value:
 *
 *                      =0    success
 *                      <0    failure



 *
 *                 
 ********************************************************************************/


int com_close(comport_t *com)
{
	if(!com)
	{
		log_error("%s The argument invalid!\n", __func__);
		return -1;
	}

	if(tcflush(com->fd, TCIOFLUSH)!=0)
	{
		printf("tcflush() failure:%s\n",  strerror(errno));

		return -1;
	}

	if(tcsetattr(com->fd, TCSANOW, &(com->oldtermios)))
	{

		printf("tcsetattr() failure:%s\n", strerror(errno));

		return -2;
	}

	close(com->fd);

	return 0;

}


